##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

require 'net/http'
require 'uri'

class MetasploitModule < Msf::Post
  include Msf::Post::File

  def initialize(info={})
    super( update_info( info,
      'Name'          => 'Multi Gather Malware Verifier',
      'Description'   => %q{
        This module will check a file for malware on VirusTotal based on the checksum.
      },
      'License'       => MSF_LICENSE,
      'Author'        => [ 'sinn3r'],
      'Platform'      => [ 'osx', 'win', 'linux' ],
      'SessionTypes'  => [ "shell", "meterpreter" ]
    ))

    register_options(
      [
        OptString.new('APIKEY',     [true, "VirusTotal API key", '501caf66349cc7357eb4398ac3298fdd03dec01a3e2f3ad576525aa7b57a1987']),
        OptString.new('REMOTEFILE', [true, "A file to check from the remote machine"])

      ])
  end

  def rhost
    session.session_host
  end

  def get_report(api_key, checksum)
    #
    # We have to use Net::HTTP instead of HttpClient because of the following error:
    # The supplied module name is ambiguous: undefined method `register_autofilter_ports'
    #
    url = URI.parse("https://www.virustotal.com/vtapi/v2/file/report")
    req = Net::HTTP::Post.new(url.path, initheader={'Host'=>'www.virustotal.com'})
    req.set_form_data({'apikey'=>api_key, 'resource'=>checksum})
    http = Net::HTTP::new(url.host, url.port)
    http.use_ssl = true
    res = http.start {|http| http.request(req)}

    unless res
      print_error("#{rhost} - Connection timed out")
      return ''
    end

    case res.code
    when 204
      print_error("#{rhost} - You have reached the request limit, please wait for one minute to try again")
      return ''
    when 403
      print_error("#{rhost} - No privilege to execute this request probably due to an invalye API key")
      return ''
    end

    body = ''
    begin
      body = JSON.parse(res.body)
    rescue JSON::ParserError
      print_error("#{rhost} - Unable to parse the response")
      return body
    end

    body
  end

  def show_report(res, filename)
    md5    = res['md5']    || ''
    sha1   = res['sha1']   || ''
    sha256 = res['sha256'] || ''

    print_status("#{rhost} - MD5: #{md5}")       unless md5.blank?
    print_status("#{rhost} - SHA1: #{sha1}")     unless sha1.blank?
    print_status("#{rhost} - SHA256: #{sha256}") unless sha256.blank?

    tbl = Rex::Text::Table.new(
      'Header'  => "Analysis Report: #{filename} (#{res['positives']} / #{res['total']}): #{res['sha256']}",
      'Indent'  => 1,
      'Columns' => ['Antivirus', 'Detected', 'Version', 'Result', 'Update']
    )

    res['scans'].each do |result|
      product  = result[0]
      detected = result[1]['detected'].to_s
      version  = result[1]['version'] || ''
      sig_name = result[1]['result']  || ''
      timestamp = result[1]['update'] || ''

      tbl << [product, detected, version, sig_name, timestamp]
    end

    report_note({
      :host => session,
      :type => 'malware.sample',
      :data => tbl.to_csv
    })
    print_status tbl.to_s
  end

  def run
    filename = datastore['REMOTEFILE']
    api_key  = datastore['APIKEY']

    unless file?(filename)
      print_error("#{rhost} - File not found: #{filename}")
      return
    end

    checksum = file_remote_digestsha1(filename)
    print_status("#{rhost} - Checking: #{filename}...")
    report   = get_report(api_key, checksum)

    return if report.blank?

    print_status("#{rhost} - VirusTotal message: #{report['verbose_msg']}")
    if report['response_code'] == 1
      show_report(report, File.basename(filename))
    end
  end
end
